#ifndef ac_tlm_cache_H_
#define ac_tlm_cache_H_


#include <systemc>
#include "ac_tlm_protocol.H"
#include "ac_tlm_port.H"

using tlm::tlm_transport_if;


namespace user
{

class cache_cell{
public:
	bool v;
	uint32_t tag;
	uint32_t *data;
};

class ac_tlm_cache :  public sc_module,  public ac_tlm_transport_if {
public:
	sc_export< ac_tlm_transport_if > target_export;
	
	ac_tlm_port DM_port;
	
	ac_tlm_rsp_status writem( const uint32_t & , const uint32_t & );
	
	ac_tlm_rsp_status readm( const uint32_t & , uint32_t & );
	
	///TRANSPORT
	ac_tlm_rsp transport( const ac_tlm_req &request ) {
		ac_tlm_rsp response;
		//cout << "\t" << request.addr <<"\n ";
		if(request.addr >= 5242884){
			//cout << "Calling" << endl;
			return DM_port->transport(request);
		}
		
		if ((request.addr % 4) == 0)
		{
			//cout << "entre1 " << "\n ";
			
			switch( request.type ) 
			{
				case READ :
					//cout << "endereco request.addr" << request.addr <<"\n ";
					//Tenta ler da cache
					response.status = readm( request.addr , response.data );
					if (response.status == SUCCESS)
					{
						//response = DM_port->transport(request);
						//cout << "TEMOS NA CACHE" << "\n ";
					}
					else if (response.status == ERROR)//mandamos pra memoria e gravamos na cache
					{
						//cout << "NAO TEMOS NA CACHE " << "\n ";
						response = DM_port->transport(request);
						//uint32_t temp = response.data;
						response.status = writem( request.addr, response.data );
					}
					break;
				case WRITE:
					//cout << "valor request.data: " << request.data <<"\n ";
					response = DM_port->transport(request);
					response.status = writem( request.addr, request.data );
					break;
				default:
					response.status = ERROR;
					break;
			}
			return response;
		}
		else //endereco desalinhado
		{
			ac_tlm_req req1, req2; 
			ac_tlm_rsp response1, response2;
			
			req1.addr = request.addr/4;
			req1.addr = req1.addr*4;
			
			req2.addr = req1.addr + 4;
			
			//cout << "Endereco1\t" << req1.addr << "\t\tEndereco2\t"<< req2.addr << "\n";
			
			
			//Para o endereco 17, 
			//shift1 = 1*8 = 8
			//shift2 = 3*8 = 24
			uint32_t shift1= (request.addr % 4)*8;
			uint32_t shift2= (4-(request.addr % 4))*8;
			
			//cout << "shift1\t"<< shift1 << "\n";
			//cout << "shift2\t"<< shift2 << "\n";
			switch( request.type ) 
			{
				case READ :
					//cout << "usando read do endereco desalinhado\t" << request.addr <<"\n ";
					//Tenta ler end1 da cache
					
					//	1 - Verificar se temos os enderecos na cache;
					// Se sim, usar eles
					// se nao, buscar na ram
					//	2 - Montar dado
					
					response1.status = readm( req1.addr , response1.data );
					if (response1.status == SUCCESS)
					{
					}
					else if (response1.status == ERROR)//mandamos pra memoria e gravamos na cache
					{
						req1.type = READ;
						response1 = DM_port->transport(req1);
						req1.type = WRITE;
						response1.status = writem( req1.addr, response1.data );
					}
					//Tenta ler end2 da cache
					response2.status = readm( req2.addr , response2.data );
					if (response2.status == SUCCESS)
					{
					}
					else if (response2.status == ERROR)//mandamos pra memoria e gravamos na cache
					{
						req2.type = READ;
						response2 = DM_port->transport(req2);
						req2.type = WRITE;
						response2.status = writem( req2.addr, response2.data );
					}
					
					//montagem do dado
					response1.data = response1.data >> shift1;
					response2.data = response2.data << shift2;
					
					response.data = response1.data + response2.data;
					response.status = SUCCESS;
					
					break;
				case WRITE:
					
					//EXEMPLO
					//GRAVAR dado do endereco 17- 20 19 18 17 - Esse dado vem do processador
					//LER 16 da MEMORIA -         19 18 17 16
					//LER 20 da MEMORIA -         23 22 21 20
					//o dado vindos da MEMORIA devem estar da seguinte maneira 
					//dado endereco 16 - 0  0  0 16 
					//dado endereco 20 - 23 22 21 0
					//dado vindo do processador para ser gravado
					//dividir d1 em - 19 18 17 0
					//dividir d2 em - 0  0  0  20					
					
					
					//cout << copia1.data << "\t" << copia2.data << "\t" << request.data << "\n";
					//Deixar o campo data do request certo
					
					req1.data = request.data;
					req2.data = request.data;
					
					req1.type = READ;
					req2.type = READ;
					
					//Ajeitando dados vindos da memoria
					response1 = DM_port->transport(req1);
					response2 = DM_port->transport(req2);
					
					response1.data = response1.data << shift2;
					response1.data = response1.data >> shift2;
					
					response2.data = response2.data >> shift1;
					response2.data = response2.data << shift1;
					
					//Ajeitando dados vindo do processador
					req1.data = req1.data << shift1;
					req2.data = req2.data >> shift2;
					
					
					req1.data = response1.data + req1.data;
					req2.data = response2.data + req2.data;
					
					req1.type = WRITE;
					req2.type = WRITE;
					
					//response = DM_port->transport(req1);
					response.status = writem( req1.addr, req1.data );
					//cout << "Endereco1\t"<< req1.addr << "\tDado1\t" << req1.data << "\n";
					response.status = writem( req2.addr, req2.data );
					//cout << "Endereco2\t"<< req2.addr << "\tDado2\t" << req2.data << "\n";
					
					response = DM_port->transport(request);
					//cout << "valor request.data: " << request.data <<"\n ";
					break;
					
				default:
					response.status = ERROR;
					break;
			}
			//response = DM_port->transport(request);
			return response;
		}
		//response = DM_port->transport(request);
		//return response;
	}
	ac_tlm_cache( sc_module_name module_name , const string name);
	~ac_tlm_cache();  

private:
	string name;
	cache_cell **memory;
	uint32_t nVias;
	uint32_t nLinhas;
	uint32_t tBloco;
	
	uint32_t round_robin;
	int hit;
	int miss;

};

};

#endif //ac_tlm_cache_H_
